package com.sl.core.engine.meta.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sl.core.domain.dto.JsonFieldDefine;
import com.sl.core.utils.JsonUtil;
import com.sl.core.engine.check.ProcessCompCheckParam;
import com.sl.core.engine.check.ProcessCompCheckResult;
import com.sl.core.engine.inject.BeanInject;
import com.sl.core.engine.log.ProcessLogChannel;
import com.sl.core.engine.meta.row.ProcessRowDescriptor;
import com.sl.core.engine.utils.BeanUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.logging.ILogChannel;
import org.apache.hop.core.logging.LogLevel;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.pipeline.Pipeline;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.transform.*;
import org.w3c.dom.Node;

import java.lang.reflect.ParameterizedType;
import java.util.*;

public abstract class AbstractProcessCompDescriptor<Main extends ITransform, Data extends ITransformData> extends BaseTransformMeta<Main, Data> {

    /**
     * 动态输入标记
     */
//    @Getter
//    @Setter
//    private Boolean dynamicInputFlag;

    @Getter
    @Setter
    private Boolean isStartStep;

    @Getter
    @Setter
    private Boolean isEndStep;

    @Getter
    protected Map<String, Object> attributeMap;

    @Getter
    @Setter
    private CompConstructor compConstructor;

    @Getter
    @Setter
    private ProcessRowDescriptor inputRowDescriptor;

    @Getter
    @Setter
    private ProcessRowDescriptor resultRowDescriptor;

    @Getter
    protected List<Class> excludeSerializableClass = Lists.newArrayList();
//
//
//    public ITransform getStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans) {
//        compConstructor = new CompConstructor(stepMeta, stepDataInterface, copyNr, transMeta, trans);
//        ProcessCompService comp = getComp();
//        compConstructor = null;
//        return comp;
//    }


//    public ITransformData getStepData() {
//        return this.getContext();
//    }

    @Override
    public ITransform createTransform(
            TransformMeta transformMeta,
            ITransformData data,
            int copyNr,
            PipelineMeta pipelineMeta,
            Pipeline pipeline) {
        try {
            ParameterizedType parameterizedType =
                    (ParameterizedType) this.getClass().getGenericSuperclass();
            Class<Main> mainClass = (Class<Main>) parameterizedType.getActualTypeArguments()[0];

            // Some tests class use BaseTransformMeta<ITransform,ITransformData>
            if (mainClass.isInterface()) return null;
            compConstructor = new CompConstructor(transformMeta, this, data, copyNr, pipelineMeta, pipeline);
            Main main = ReflectUtil.newInstance(mainClass, this);

            BeanInject.INSTANCE.beanInject(main);

            return main;
        } catch (IllegalArgumentException e) {
            throw new RuntimeException("Error create instance of transform: " + this.getName(), e);
        }

    }

    @Override
    public ILogChannel getLog() {
        if (log == null) {
            log = new ProcessLogChannel(this, getParent());
        }
        return log;
    }

    @SneakyThrows
    @Override
    public void loadXml(Node transformNode, IHopMetadataProvider metadataProvider) {
        this.attributeMap = Maps.newHashMap();
        Map<String, Object> map = XmlUtil.xmlToMap(transformNode);
        if (CollUtil.isEmpty(map)) {
            return;
        }
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String k = entry.getKey();
            Object v = entry.getValue();
            if (StrUtil.equalsAnyIgnoreCase(k, "PARTITIONING_METHOD", "code", "type", "copies", "name", "partitioning", "cluster_schema", "PARTITIONING_SCHEMA", "remotesteps", "row_distribution_code", "custom_distribution", "distribute", "attributes")) {
                continue;
            }
            this.attributeMap.put(k, v);
        }
        this.loadDescriptor(attributeMap);
    }

    protected void putAttr(String key, String value) {
        this.attributeMap.put(key, value);
    }


    public void loadDescriptor(Map<String, Object> map) {

    }

    @Override
    public void check(
            List<ICheckResult> remarks,
            PipelineMeta pipelineMeta,
            TransformMeta transformMeta,
            IRowMeta prev,
            String[] input,
            String[] output,
            IRowMeta info,
            IVariables variables,
            IHopMetadataProvider metadataProvider) {

        ProcessCompCheckParam processCompCheckParam = new ProcessCompCheckParam();
        processCompCheckParam.setPrevProcessRowDescriptor(new ProcessRowDescriptor(prev));
        processCompCheckParam.setInput(Arrays.stream(input).toList());
        processCompCheckParam.setOutput(Arrays.stream(output).toList());
        processCompCheckParam.setStepCode(transformMeta.getName());
        //校验
        List<ProcessCompCheckResult> check = check(processCompCheckParam);

        if (CollUtil.isEmpty(check)) {
            return;
        }
        remarks.addAll(check);
//        check.forEach(v -> {
//            ProcessCompCheckResult checkResult = new ProcessCompCheckResult();
//            checkResult.setErrorCode(v.getErrorCode());
//            checkResult.setText(v.getText());
//            checkResult.setStepCode(transformMeta.getName());
//            checkResult.setType(v.getType());
//            remarks.add(checkResult);
//        });
    }

    public List<ProcessCompCheckResult> check(ProcessCompCheckParam processCompCheckParam) {
        return Collections.emptyList();
    }

    protected List<ProcessCompCheckResult> checkWrapper(ProcessCompCheckResult processCompCheckResult) {
        List<ProcessCompCheckResult> results = Lists.newArrayList();
        if (ObjectUtil.isNotNull(processCompCheckResult)) {
            results.add(processCompCheckResult);
        }
        return results;
    }

    protected ProcessCompCheckResult checkOnlyOneInput(ProcessCompCheckParam processCompCheckParam) {
        List<String> input = processCompCheckParam.getInput();

        if (CollUtil.size(input) > 1) {
            return ProcessCompCheckResult.normalErrorMessage(processCompCheckParam.getStepCode(), "只能输入一个组件");
        }
        return null;
    }

    public RowMeta getResultDescriptor() {
        RowMeta inputRowMeta = new ProcessRowDescriptor();
        return this.getResultDescriptor(inputRowMeta);
    }

    public RowMeta getResultDescriptor(RowMeta inputRowMeta) {
        if (ObjectUtil.isNull(inputRowMeta)) {
            return getResultDescriptor();
        }
        if (inputRowMeta instanceof ProcessRowDescriptor rowDescriptor) {
            return getResultDescriptor(rowDescriptor);
        }
        ProcessRowDescriptor rowDescriptor = new ProcessRowDescriptor();
        rowDescriptor.addRowMeta(inputRowMeta);
        return getResultDescriptor(rowDescriptor);
    }

    public abstract ProcessRowDescriptor getResultDescriptor(ProcessRowDescriptor rowDescriptor);

    @Override
    public String getXml() {
        StringBuilder retval = new StringBuilder(300);
        for (Map.Entry<String, Object> entry : this.attributeMap.entrySet()) {
            String k = entry.getKey();
            Object v = entry.getValue();
            if (StrUtil.equalsAnyIgnoreCase(k, "PARTITIONING_METHOD", "code", "type", "copies", "name", "partitioning", "cluster_schema", "PARTITIONING_SCHEMA", "remotesteps", "row_distribution_code", "custom_distribution", "distribute", "attributes")) {
                continue;
            }
            String xml = this.buildXml(k, v);
            retval.append(xml);
        }
        return retval.toString();
    }

    public String buildXml(String key, Object v) {
        StringBuilder retval = new StringBuilder(300);

        if (v instanceof Map) {
            retval.append("    ").append(XmlUtil.mapToXmlStr((Map<?, ?>) v, key, null, true));
        } else if (v instanceof Collection<?>) {
            ((Collection<?>) v).forEach(vv -> {
                String xml = this.buildXml(key, vv);
                retval.append("    ").append(xml);
            });
        } else {
            retval.append("    ").append(XmlHandler.addTagValue(key, JsonUtil.toStr(v)));
        }
        return retval.toString();

    }

    public String getStrAttr(String key) {
        return BeanUtil.getStr(attributeMap, key);
    }

    public Long getLongAttr(String key) {
        return BeanUtil.getLong(attributeMap, key);
    }


    public ProcessRowDescriptor buildResultProcessRowDescriptor(Map<String, Object> map) {
        if (ObjectUtil.isNull(map)) {
            return null;
        }
        Boolean isArrayFlag = MapUtil.getBool(map, "isArrayFlag", false);
        Boolean basicTypeFlag = MapUtil.getBool(map, "basicTypeFlag", false);
        String basicFieldType = MapUtil.getStr(map, "basicFieldType", ProcessRowDescriptor.defaultValueType());
        List<Map<String, Object>> inputJsonDefine = getListMapAttr(map, "params");
        if (CollUtil.isEmpty(inputJsonDefine) && BooleanUtil.isFalse(basicTypeFlag)) {
            return null;
        }
        List<JsonFieldDefine> inputFieldDefine = JsonUtil.toList(inputJsonDefine, JsonFieldDefine.class);

        resultRowDescriptor = new ProcessRowDescriptor();
        resultRowDescriptor.addFieldDefine(inputFieldDefine);
        resultRowDescriptor.setIsArray(isArrayFlag);
        resultRowDescriptor.setBasicFieldType(basicFieldType);
        resultRowDescriptor.setBasicTypeFlag(basicTypeFlag);

        return resultRowDescriptor;
    }

    public void buildInputProcessRowDescriptor(Map<String, Object> map) {

        Boolean isArrayFlag = MapUtil.getBool(map, "isArrayFlag", false);
        Boolean basicTypeFlag = MapUtil.getBool(map, "basicTypeFlag", false);
        String basicFieldType = MapUtil.getStr(map, "basicFieldType", ProcessRowDescriptor.defaultValueType());
        List<Map<String, Object>> inputJsonDefine = getListMapAttr(map, "params");
        List<JsonFieldDefine> inputFieldDefine = JsonUtil.toList(inputJsonDefine, JsonFieldDefine.class);

        inputRowDescriptor = new ProcessRowDescriptor();
        inputRowDescriptor.addFieldDefine(inputFieldDefine);
        inputRowDescriptor.setIsArray(isArrayFlag);
        inputRowDescriptor.setBasicFieldType(basicFieldType);
        inputRowDescriptor.setBasicTypeFlag(basicTypeFlag);
    }

    public Map<String, Object> getJsonDefine(String key) {
        //输入定义
        Map<String, Object> property = BeanUtil.getProperty(attributeMap, key, Map.class, null);
        return property;
    }

    public List<Map<String, Object>> getListMapAttr(Map<String, Object> attributeMap, String key) {
        if (MapUtil.isEmpty(attributeMap)) {
            return Collections.emptyList();
        }
        Object o = BeanUtil.getProperty(attributeMap, key);
        if (ObjectUtil.isNull(o)) {
            return Lists.newArrayList();
        }
        if (o instanceof String) {
            return Collections.emptyList();
        }
        if (o instanceof Collection<?> obj) {
            childrenToList((List<Map<String, Object>>) obj);
        }
        if (o instanceof Map om) {
            o = Lists.newArrayList(om);
            childrenToList((List<Map<String, Object>>) o);
        }
        return (List<Map<String, Object>>) o;
    }

    public void childrenToList(List<Map<String, Object>> list) {
        if (CollUtil.isEmpty(list)) {
            return;
        }
        for (Map<String, Object> map : list) {
            Object o = map.get("children");
            if (ObjectUtil.isNotNull(o) && o instanceof String) {
                o = null;
                map.put("children", null);
            }
            if (ObjectUtil.isNotNull(o)) {
                List<Map<String, Object>> children = getListMapAttr(map, "children");
                if (CollUtil.isNotEmpty(children)) {
                    map.put("children", children);
                    childrenToList(children);
                }
            }

        }
    }

    public List<Map<String, Object>> getListMapAttr(String key) {
        return getListMapAttr(this.attributeMap, key);
    }

    public Map<String, Object> getMapAttr(String key) {
        return BeanUtil.getMap(attributeMap, key);
    }

    public List<String> getListAttr(String key) {
        Object o = this.attributeMap.get(key);
        if (ObjectUtil.isNull(o)) {
            return Lists.newArrayList();
        }
        if (o instanceof Collection<?>) {
            return (List<String>) o;
        }
        return StrUtil.split(o.toString(), ",");
    }

    public Integer getIntAttr(String key) {
        return this.getIntAttr(key, null);
    }

    public Integer getIntAttr(String key, Integer defaultValue) {
        Object o = BeanUtil.getInteger(attributeMap, key);

        if (ObjectUtil.isNull(o)) {
            return defaultValue;
        }
        return Convert.convert(Integer.class, o);
    }

    public Boolean getBoolAttr(String key) {
        return BeanUtil.getBool(attributeMap, key);
    }


    @Override
    public void getFields(
            PipelineMeta pipelineMeta,
            IRowMeta inputRowMeta,
            String name,
            IRowMeta[] info,
            TransformMeta nextTransform,
            IVariables variables,
            IHopMetadataProvider metadataProvider)
            throws HopTransformException {
        this.compConstructor = new CompConstructor(
                null, null, null, 0, pipelineMeta, null
        );

        RowMeta rowMeta = new ProcessRowDescriptor(inputRowMeta);
        RowMeta resultDescriptor = getResultDescriptor(rowMeta);
        inputRowMeta.clear();
        if (ObjectUtil.isNotNull(resultDescriptor)) {
            inputRowMeta.addRowMeta(resultDescriptor);
        }

    }


    public <T> T get(String seleniumConfig) {
        if(ObjectUtil.isNull(this.attributeMap)){
            return null;
        }
        Object o = this.attributeMap.get(seleniumConfig);
        return (T) o;
    }

    public LogLevel getLogLevel(LogLevel lv) {
        String strAttr = getStrAttr("logLevel");
        if (StrUtil.isNotBlank(strAttr)) {
            return LogLevel.lookupCode(strAttr);
        }
        return lv;
    }


    @Getter
    public static class CompConstructor {

        private TransformMeta stepMeta;
        private ITransformMeta transformMeta;
        private ITransformData stepDataInterface;
        private int copyNr;
        private PipelineMeta transMeta;
        private Pipeline trans;

        public CompConstructor(TransformMeta stepMeta, ITransformMeta transformMeta, ITransformData stepDataInterface, int copyNr, PipelineMeta transMeta, Pipeline trans) {
            this.stepMeta = stepMeta;
            this.transformMeta = transformMeta;
            this.stepDataInterface = stepDataInterface;
            this.copyNr = copyNr;
            this.transMeta = transMeta;
            this.trans = trans;
        }
    }

}
